1 /**
2 Copyright: Copyright (c) 2018, Joakim Brännström. All rights reserved.
3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 
6 This module contains the registry of analaysers
7 */
8 module code_checker.engine.registry;
9 
10 import logger = std.experimental.logger;
11 import std.exception : collectException;
12 
13 import code_checker.engine.types;
14 
15 @safe:
16 
17 /// The type of an analyser which then affect the order they are executed.
18 enum Type {
19     staticCode,
20     dynamic,
21 }
22 
23 auto makeRegistry() {
24     import code_checker.engine;
25 
26     Registry reg;
27     reg.put(new ClangTidy, Type.staticCode);
28     reg.put(new IncludeWhatYouUse, Type.staticCode);
29     return reg;
30 }
31 
32 struct Registry {
33     private {
34         BaseFixture[][Type] analysers;
35     }
36 
37     void put(BaseFixture a, Type t) {
38         assert(a !is null);
39 
40         if (auto v = t in analysers) {
41             (*v) ~= a;
42         } else {
43             analysers[t] = [a];
44         }
45     }
46 
47     /// Range over the analysers.
48     auto range() {
49         import std.array : array;
50         import std.algorithm : map, joiner, filter;
51 
52         static immutable order = [Type.staticCode, Type.dynamic];
53 
54         auto getAnalysers(Type t) {
55             if (auto v = t in analysers)
56                 return (*v).map!(a => AnalyserRange.Pair(t, a)).array;
57             return null;
58         }
59 
60         return order.map!(a => getAnalysers(a))
61             .filter!(a => a !is null)
62             .joiner
63             .array;
64     }
65 }
66 
67 /** Run the `checkers` from `reg` inside `env`.
68  *
69  * Returns: The total status of running the analyzers.
70  */
71 TotalResult execute(Environment env, string[] analysers, ref Registry reg) @trusted {
72     import std.algorithm;
73     import std.range;
74 
75     TotalResult tres;
76 
77     void handleResult(Result res_) nothrow {
78         // we know the thread finished and have the only copy.
79         // immutable is a bit cumbersome for now so throw away it to keep the
80         // code somewhat efficient.
81         auto res = cast() res_;
82 
83         try {
84             log(res.msg);
85 
86             tres.status = mergeStatus(tres.status, res.status);
87             tres.score = Score(tres.score + res.score);
88             tres.supp = Suppressed(tres.supp + res.supp);
89             tres.sugg ~= res.msg.array.filter!(a => a.severity == MsgSeverity.improveSuggestion)
90                 .array;
91             tres.failed ~= res.failed;
92             tres.success ~= res.success;
93 
94             logger.trace(res);
95             logger.trace(tres);
96         } catch (Exception e) {
97             logger.warning("Failed executing all tests").collectException;
98             logger.warning(e.msg).collectException;
99             tres.status = Status.failed;
100         }
101     }
102 
103     foreach (a; reg.range.filter!((a) {
104             if (analysers.empty)
105                 return true;
106             return analysers.canFind(a.analyzer.name);
107         })) {
108         logger.infof("%s: %s", a.type, a.analyzer.explain);
109         a.analyzer.putEnv(env);
110         handleResult(executeOneAnalyzer(a.analyzer));
111     }
112 
113     log(tres);
114     return tres;
115 }
116 
117 Result executeOneAnalyzer(BaseFixture a) nothrow @trusted {
118     Result r;
119     try {
120         a.setup;
121         a.execute;
122         a.tearDown;
123         r = a.result;
124     } catch (Exception e) {
125         logger.error(e.msg).collectException;
126         r.status = Status.failed;
127     }
128 
129     return r;
130 }
131 
132 private:
133 
134 void log(Messages msgs) {
135     import std.algorithm : sort;
136 
137     foreach (m; msgs.value.sort) {
138         final switch (m.severity) {
139         case MsgSeverity.improveSuggestion:
140             break;
141         case MsgSeverity.unableToExecute:
142         case MsgSeverity.failReason:
143             logger.warning(m.value);
144             break;
145         case MsgSeverity.trace:
146             logger.trace(m.value);
147             break;
148         }
149     }
150 }
151 
152 void log(TotalResult tres) {
153     import std.conv : to;
154     import colorlog;
155 
156     logger.infof("Analyzers reported %s", tres.status == Status.failed
157             ? "Failed".color(Color.red) : "Passed".color(Color.green));
158 
159     if (tres.sugg.length > 0) {
160         logger.info("Suggestions for how to improve the score");
161         foreach (m; tres.sugg)
162             logger.info("    ", m.value);
163     }
164 
165     if (tres.supp != 0) {
166         logger.infof("You suppressed %s warnings", tres.supp);
167     }
168 
169     if (tres.status == Status.passed) {
170         logger.info("Congratulations!!!");
171     }
172 
173     string score() {
174         return tres.score < 0 ? tres.score.to!string.color(Color.red)
175             .mode(Mode.bold).toString : tres.score.to!string;
176     }
177 
178     logger.infof("You scored %s points", score);
179 }
180 
181 /// Input range over the analysers.
182 struct AnalyserRange {
183     import std.typecons : Tuple;
184 
185     alias Pair = Tuple!(Type, "type", BaseFixture, "analyzer");
186 
187     Pair[] r;
188 
189     auto front() @safe pure nothrow {
190         assert(!empty, "Can't get front of an empty range");
191         return r[0];
192     }
193 
194     void popFront() @safe pure nothrow {
195         assert(!empty, "Can't pop front of an empty range");
196         r = r[1 .. $];
197     }
198 
199     bool empty() @safe pure nothrow const @nogc {
200         return r.length == 0;
201     }
202 }